1   /*
2    * Copyright (c) 1995, 2011, Oracle and/or its affiliates. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions
6    * are met:
7    *
8    *   - Redistributions of source code must retain the above copyright
9    *     notice, this list of conditions and the following disclaimer.
10   *
11   *   - Redistributions in binary form must reproduce the above copyright
12   *     notice, this list of conditions and the following disclaimer in the
13   *     documentation and/or other materials provided with the distribution.
14   *
15   *   - Neither the name of Oracle nor the names of its
16   *     contributors may be used to endorse or promote products derived
17   *     from this software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20   * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21   * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30   */
31  
32  
33  import java.applet.Applet;
34  import java.awt.Graphics;
35  import java.awt.Color;
36  import java.awt.event.*;
37  import java.io.*;
38  import java.net.URL;
39  
40  
41  /* A set of classes to parse, represent and display 3D wireframe models
42  represented in Wavefront .obj format. */
43  @SuppressWarnings("serial")
44  class FileFormatException extends Exception {
45  
46      public FileFormatException(String s) {
47          super(s);
48      }
49  }
50  
51  
52  /** The representation of a 3D model */
53  final class Model3D {
54  
55      float vert[];
56      int tvert[];
57      int nvert, maxvert;
58      int con[];
59      int ncon, maxcon;
60      boolean transformed;
61      Matrix3D mat;
62      float xmin, xmax, ymin, ymax, zmin, zmax;
63  
64      Model3D() {
65          mat = new Matrix3D();
66          mat.xrot(20);
67          mat.yrot(30);
68      }
69  
70      /** Create a 3D model by parsing an input stream */
71      Model3D(InputStream is) throws IOException, FileFormatException {
72          this();
73          StreamTokenizer st = new StreamTokenizer(
74                  new BufferedReader(new InputStreamReader(is, "UTF-8")));
75          st.eolIsSignificant(true);
76          st.commentChar('#');
77          scan:
78          while (true) {
79              switch (st.nextToken()) {
80                  default:
81                      break scan;
82                  case StreamTokenizer.TT_EOL:
83                      break;
84                  case StreamTokenizer.TT_WORD:
85                      if ("v".equals(st.sval)) {
86                          double x = 0, y = 0, z = 0;
87                          if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
88                              x = st.nval;
89                              if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
90                                  y = st.nval;
91                                  if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
92                                      z = st.nval;
93                                  }
94                              }
95                          }
96                          addVert((float) x, (float) y, (float) z);
97                          while (st.ttype != StreamTokenizer.TT_EOL && st.ttype
98                                  != StreamTokenizer.TT_EOF) {
99                              st.nextToken();
100                         }
101                     } else if ("f".equals(st.sval) || "fo".equals(st.sval) || "l".
102                             equals(st.sval)) {
103                         int start = -1;
104                         int prev = -1;
105                         int n = -1;
106                         while (true) {
107                             if (st.nextToken() == StreamTokenizer.TT_NUMBER) {
108                                 n = (int) st.nval;
109                                 if (prev >= 0) {
110                                     add(prev - 1, n - 1);
111                                 }
112                                 if (start < 0) {
113                                     start = n;
114                                 }
115                                 prev = n;
116                             } else if (st.ttype == '/') {
117                                 st.nextToken();
118                             } else {
119                                 break;
120                             }
121                         }
122                         if (start >= 0) {
123                             add(start - 1, prev - 1);
124                         }
125                         if (st.ttype != StreamTokenizer.TT_EOL) {
126                             break scan;
127                         }
128                     } else {
129                         while (st.nextToken() != StreamTokenizer.TT_EOL
130                                 && st.ttype != StreamTokenizer.TT_EOF) {
131                             // no-op
132                         }
133                     }
134             }
135         }
136         is.close();
137         if (st.ttype != StreamTokenizer.TT_EOF) {
138             throw new FileFormatException(st.toString());
139         }
140     }
141 
142     /** Add a vertex to this model */
143     int addVert(float x, float y, float z) {
144         int i = nvert;
145         if (i >= maxvert) {
146             if (vert == null) {
147                 maxvert = 100;
148                 vert = new float[maxvert * 3];
149             } else {
150                 maxvert *= 2;
151                 float nv[] = new float[maxvert * 3];
152                 System.arraycopy(vert, 0, nv, 0, vert.length);
153                 vert = nv;
154             }
155         }
156         i *= 3;
157         vert[i] = x;
158         vert[i + 1] = y;
159         vert[i + 2] = z;
160         return nvert++;
161     }
162 
163     /** Add a line from vertex p1 to vertex p2 */
164     void add(int p1, int p2) {
165         int i = ncon;
166         if (p1 >= nvert || p2 >= nvert) {
167             return;
168         }
169         if (i >= maxcon) {
170             if (con == null) {
171                 maxcon = 100;
172                 con = new int[maxcon];
173             } else {
174                 maxcon *= 2;
175                 int nv[] = new int[maxcon];
176                 System.arraycopy(con, 0, nv, 0, con.length);
177                 con = nv;
178             }
179         }
180         if (p1 > p2) {
181             int t = p1;
182             p1 = p2;
183             p2 = t;
184         }
185         con[i] = (p1 << 16) | p2;
186         ncon = i + 1;
187     }
188 
189     /** Transform all the points in this model */
190     void transform() {
191         if (transformed || nvert <= 0) {
192             return;
193         }
194         if (tvert == null || tvert.length < nvert * 3) {
195             tvert = new int[nvert * 3];
196         }
197         mat.transform(vert, tvert, nvert);
198         transformed = true;
199     }
200 
201     /* Quick Sort implementation
202      */
203     private void quickSort(int a[], int left, int right) {
204         int leftIndex = left;
205         int rightIndex = right;
206         int partionElement;
207         if (right > left) {
208 
209             /* Arbitrarily establishing partition element as the midpoint of
210              * the array.
211              */
212             partionElement = a[(left + right) / 2];
213 
214             // loop through the array until indices cross
215             while (leftIndex <= rightIndex) {
216                 /* find the first element that is greater than or equal to
217                  * the partionElement starting from the leftIndex.
218                  */
219                 while ((leftIndex < right) && (a[leftIndex] < partionElement)) {
220                     ++leftIndex;
221                 }
222 
223                 /* find an element that is smaller than or equal to
224                  * the partionElement starting from the rightIndex.
225                  */
226                 while ((rightIndex > left) && (a[rightIndex] > partionElement)) {
227                     --rightIndex;
228                 }
229 
230                 // if the indexes have not crossed, swap
231                 if (leftIndex <= rightIndex) {
232                     swap(a, leftIndex, rightIndex);
233                     ++leftIndex;
234                     --rightIndex;
235                 }
236             }
237 
238             /* If the right index has not reached the left side of array
239              * must now sort the left partition.
240              */
241             if (left < rightIndex) {
242                 quickSort(a, left, rightIndex);
243             }
244 
245             /* If the left index has not reached the right side of array
246              * must now sort the right partition.
247              */
248             if (leftIndex < right) {
249                 quickSort(a, leftIndex, right);
250             }
251 
252         }
253     }
254 
255     private void swap(int a[], int i, int j) {
256         int T;
257         T = a[i];
258         a[i] = a[j];
259         a[j] = T;
260     }
261 
262     /** eliminate duplicate lines */
263     void compress() {
264         int limit = ncon;
265         int c[] = con;
266         quickSort(con, 0, ncon - 1);
267         int d = 0;
268         int pp1 = -1;
269         for (int i = 0; i < limit; i++) {
270             int p1 = c[i];
271             if (pp1 != p1) {
272                 c[d] = p1;
273                 d++;
274             }
275             pp1 = p1;
276         }
277         ncon = d;
278     }
279     static Color gr[];
280 
281     /** Paint this model to a graphics context.  It uses the matrix associated
282     with this model to map from model space to screen space.
283     The next version of the browser should have double buffering,
284     which will make this *much* nicer */
285     void paint(Graphics g) {
286         if (vert == null || nvert <= 0) {
287             return;
288         }
289         transform();
290         if (gr == null) {
291             gr = new Color[16];
292             for (int i = 0; i < 16; i++) {
293                 int grey = (int) (170 * (1 - Math.pow(i / 15.0, 2.3)));
294                 gr[i] = new Color(grey, grey, grey);
295             }
296         }
297         int lg = 0;
298         int lim = ncon;
299         int c[] = con;
300         int v[] = tvert;
301         if (lim <= 0 || nvert <= 0) {
302             return;
303         }
304         for (int i = 0; i < lim; i++) {
305             int T = c[i];
306             int p1 = ((T >> 16) & 0xFFFF) * 3;
307             int p2 = (T & 0xFFFF) * 3;
308             int grey = v[p1 + 2] + v[p2 + 2];
309             if (grey < 0) {
310                 grey = 0;
311             }
312             if (grey > 15) {
313                 grey = 15;
314             }
315             if (grey != lg) {
316                 lg = grey;
317                 g.setColor(gr[grey]);
318             }
319             g.drawLine(v[p1], v[p1 + 1],
320                     v[p2], v[p2 + 1]);
321         }
322     }
323 
324     /** Find the bounding box of this model */
325     void findBB() {
326         if (nvert <= 0) {
327             return;
328         }
329         float v[] = vert;
330         float _xmin = v[0], _xmax = _xmin;
331         float _ymin = v[1], _ymax = _ymin;
332         float _zmin = v[2], _zmax = _zmin;
333         for (int i = nvert * 3; (i -= 3) > 0;) {
334             float x = v[i];
335             if (x < _xmin) {
336                 _xmin = x;
337             }
338             if (x > _xmax) {
339                 _xmax = x;
340             }
341             float y = v[i + 1];
342             if (y < _ymin) {
343                 _ymin = y;
344             }
345             if (y > _ymax) {
346                 _ymax = y;
347             }
348             float z = v[i + 2];
349             if (z < _zmin) {
350                 _zmin = z;
351             }
352             if (z > _zmax) {
353                 _zmax = z;
354             }
355         }
356         this.xmax = _xmax;
357         this.xmin = _xmin;
358         this.ymax = _ymax;
359         this.ymin = _ymin;
360         this.zmax = _zmax;
361         this.zmin = _zmin;
362     }
363 }
364 
365 
366 /** An applet to put a 3D model into a page */
367 @SuppressWarnings("serial")
368 public class ThreeD extends Applet
369         implements Runnable, MouseListener, MouseMotionListener {
370 
371     Model3D md;
372     boolean painted = true;
373     float xfac;
374     int prevx, prevy;
375     float scalefudge = 1;
376     Matrix3D amat = new Matrix3D(), tmat = new Matrix3D();
377     String mdname = null;
378     String message = null;
379 
380     @Override
381     public void init() {
382         mdname = getParameter("model");
383         try {
384             scalefudge = Float.valueOf(getParameter("scale")).floatValue();
385         } catch (Exception ignored) {
386             // fall back to default scalefudge = 1
387         }
388         amat.yrot(20);
389         amat.xrot(20);
390         if (mdname == null) {
391             mdname = "model.obj";
392         }
393         resize(getSize().width <= 20 ? 400 : getSize().width,
394                 getSize().height <= 20 ? 400 : getSize().height);
395         addMouseListener(this);
396         addMouseMotionListener(this);
397     }
398 
399     @Override
400     public void destroy() {
401         removeMouseListener(this);
402         removeMouseMotionListener(this);
403     }
404 
405     @Override
406     public void run() {
407         InputStream is = null;
408         try {
409             Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
410             is = new URL(getDocumentBase(), mdname).openStream();
411             Model3D m = new Model3D(is);
412             md = m;
413             m.findBB();
414             m.compress();
415             float xw = m.xmax - m.xmin;
416             float yw = m.ymax - m.ymin;
417             float zw = m.zmax - m.zmin;
418             if (yw > xw) {
419                 xw = yw;
420             }
421             if (zw > xw) {
422                 xw = zw;
423             }
424             float f1 = getSize().width / xw;
425             float f2 = getSize().height / xw;
426             xfac = 0.7f * (f1 < f2 ? f1 : f2) * scalefudge;
427         } catch (Exception e) {
428             md = null;
429             message = e.toString();
430         }
431         try {
432             if (is != null) {
433                 is.close();
434             }
435         } catch (Exception e) {
436         }
437         repaint();
438     }
439 
440     @Override
441     public void start() {
442         if (md == null && message == null) {
443             new Thread(this).start();
444         }
445     }
446 
447     @Override
448     public void stop() {
449     }
450 
451     @Override
452     public void mouseClicked(MouseEvent e) {
453     }
454 
455     @Override
456     public void mousePressed(MouseEvent e) {
457         prevx = e.getX();
458         prevy = e.getY();
459         e.consume();
460     }
461 
462     @Override
463     public void mouseReleased(MouseEvent e) {
464     }
465 
466     @Override
467     public void mouseEntered(MouseEvent e) {
468     }
469 
470     @Override
471     public void mouseExited(MouseEvent e) {
472     }
473 
474     @Override
475     public void mouseDragged(MouseEvent e) {
476         int x = e.getX();
477         int y = e.getY();
478 
479         tmat.unit();
480         float xtheta = (prevy - y) * 360.0f / getSize().width;
481         float ytheta = (x - prevx) * 360.0f / getSize().height;
482         tmat.xrot(xtheta);
483         tmat.yrot(ytheta);
484         amat.mult(tmat);
485         if (painted) {
486             painted = false;
487             repaint();
488         }
489         prevx = x;
490         prevy = y;
491         e.consume();
492     }
493 
494     @Override
495     public void mouseMoved(MouseEvent e) {
496     }
497 
498     @Override
499     public void paint(Graphics g) {
500         if (md != null) {
501             md.mat.unit();
502             md.mat.translate(-(md.xmin + md.xmax) / 2,
503                     -(md.ymin + md.ymax) / 2,
504                     -(md.zmin + md.zmax) / 2);
505             md.mat.mult(amat);
506             md.mat.scale(xfac, -xfac, 16 * xfac / getSize().width);
507             md.mat.translate(getSize().width / 2, getSize().height / 2, 8);
508             md.transformed = false;
509             md.paint(g);
510             setPainted();
511         } else if (message != null) {
512             g.drawString("Error in model:", 3, 20);
513             g.drawString(message, 10, 40);
514         }
515     }
516 
517     private synchronized void setPainted() {
518         painted = true;
519         notifyAll();
520     }
521 
522     @Override
523     public String getAppletInfo() {
524         return "Title: ThreeD \nAuthor: James Gosling? \n"
525                 + "An applet to put a 3D model into a page.";
526     }
527 
528     @Override
529     public String[][] getParameterInfo() {
530         String[][] info = {
531             { "model", "path string", "The path to the model to be displayed." },
532             { "scale", "float", "The scale of the model.  Default is 1." }
533         };
534         return info;
535     }
536 }